/*  

Cafechess Library: Cover basic chess programming needs, like GUI, Engine integration, 
FEN and PGN parsing.

                 \\|//
                 (o o)
--------------ooO-(_)-Ooo----------------------------------
Copyright (C) 2009  By Gregory A. Prentice Snoqualmie, WA
                    www.cafechess.org
                    cafechess@comcast.net
                      Oooo
--------------oooO----(  )---------------------------------
              (  )    (_/
               \_)

Microsoft Public License (Ms-PL)
               
Revisions:

Author        Date    Description:

*/
using System;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;

using Cafechess.Chess.General;
using Cafechess.Imaging;
using Cafechess.Imaging.Draw;
using Cafechess.Chess.Validation;
using Cafechess.Chess.Parsers;

namespace Cafechess.Chess.Controls
{

  /// <summary>
  /// Enum used to select a specific layer to perform specific drawing
  /// action upon.
  /// <seealso cref="IChessboardGui"/>
  /// </summary>
  public enum Layers
  {
    /// <summary>
    /// Boarder of the chess board.
    /// </summary>
    Boarder, 
    /// <summary>
    /// The main board, where squares are drawn.
    /// </summary>
    Board, 
    /// <summary>
    /// This is a drawing layer where you may add arrows and such.
    /// The images will appear under the pieces but ontop of the
    /// squares.
    /// </summary>
    DrawMid, 
    /// <summary>
    /// The is the layer where pieces are drawn.
    /// </summary>
    Piece, 
    /// <summary>
    /// This is a drawing layer where you may add arrows and such.
    /// The images will appear over the pieces.
    /// </summary>
    DrawTop, 
    /// <summary>
    /// This is the container for all of the rendered layers.  Normally
    /// used to display bubble text.
    /// </summary>
    Surface
  };

  /// <summary>
	/// The main chessboard layer which contains all the other layers.  Provides
	/// the glue for combining all our images into the final product.
	/// </summary>
	public class ChessSurface : Surface, IChessboardGui
	{

    #region Members
    /// <summary>
    /// Parent control that we shall be drawing on.
    /// </summary>
    private System.Windows.Forms.Control coControl;
    private Color coAttacksColor;
    /// <summary>
    /// The size of our surface.
    /// </summary>
    private Rectangle coBoardSquare;
    /// <summary>
    /// Contains an array of our PieceSquare objects that are
    /// shared among all of our layers.
    /// </summary>
    Hashtable coChessBoard;
    /// <summary>
    /// Parser for setting up positions via standard FEN.
    /// </summary>
    FenNotation coFenNotation;    
    bool coIsFlipped;
    bool coIsEditting;
    bool coIsDrawing;
    Layers coLayers;
    Layer coCurrentLayer;

    #endregion

    #region Properties

  

    public override Rectangle Square
    {
      get
      {
        return base.Square;
      }
      set
      {

        // Determine if our value is a rectangle, if it is
        //  then this code will ensure we have a square in the
        //  rectangle.
        Rectangle square = new Rectangle();
        if( value.Height > value.Width )
        {
          square.Height = value.Width;
          square.Width = value.Width;
        }
        else
        {
          square.Height = value.Height;      
          square.Width  = value.Height;
        }
        if(!square.IsEmpty)
        {

          base.Square = square;
          if( Count > 4 )
          {
            BoarderLayer.Square = square;
            BoardLayer.Square = square;
            PieceLayer.Square = square; 
            MidDrawingLayer.Square = square;
            TopDrawingLayer.Square = square;
            AdjustForBoarder();
            TopDrawingLayer.Resized();
            MidDrawingLayer.Resized();
          }
        }
      }
    }

    public System.Windows.Forms.Control Control
    { 
      get{ return coControl; }
      set{ coControl = value;}
    }

    BoarderLayer coBoarderLayer;
    public BoarderLayer BoarderLayer
    { 
      get{ return coBoarderLayer; }
    }
    BoardLayer coBoardLayer;
    public BoardLayer BoardLayer
    { 
      get{ return coBoardLayer;} 
    }
    DrawingLayer coMidDrawingLayer;
    public DrawingLayer MidDrawingLayer
    { 
      get{ return coMidDrawingLayer;} 
    }
    PieceLayer coPieceLayer;
    public PieceLayer PieceLayer
    { 
      get{ return coPieceLayer; }
    }
    DrawingLayer coTopDrawingLayer;
    public DrawingLayer TopDrawingLayer
    { 
      get{ return coTopDrawingLayer;} 
    }

    #endregion Properties

    MoveGeneration coMoveInput;

    #region Constructors

    /// <summary>
    /// Construct our surface object that will manage the physical drawing bitmap
    /// and the order in which our layers are applied to the image.
    /// </summary>
    /// <param name="rectangle"></param>
    public ChessSurface(Rectangle rectangle, Color background) : base(rectangle,background)
    {
      coMoveInput = MoveGeneration.WhiteGUIvsBlackGUI;
      coIsEditting = false;
      coIsDrawing = false;
      coBoarderLayer = new BoarderLayer(rectangle,Color.Magenta);
      coBoarderLayer.Name = "Boarder";
      coBoardLayer = new BoardLayer(rectangle,Color.Magenta);
      coBoardLayer.Name = "Board";
      coMidDrawingLayer = new DrawingLayer(rectangle,Color.Magenta);
      coMidDrawingLayer.Name = "MidDrawingLayer";
      coPieceLayer = new PieceLayer(this, rectangle,Color.Magenta);
      coPieceLayer.Name = "PieceLayer";
      coTopDrawingLayer = new DrawingLayer(rectangle,Color.Magenta);
      coTopDrawingLayer.Name = "TopDrawingLayer";
      coAttacksColor = Color.LightSteelBlue;
      Layer(coBoarderLayer);
      Layer(coBoardLayer);
      Layer(coMidDrawingLayer);
      Layer(coPieceLayer);
      Layer(coTopDrawingLayer);
      BoarderLayer.Parent = this;
      BoardLayer.Parent = this;
      PieceLayer.Parent = this;      
      EventSquareGenerated +=new SquareGenerated(PieceLayer.SquareGenerated);
      EventSquareGenerated +=new SquareGenerated(BoardLayer.SquareGenerated);
      MidDrawingLayer.Parent = this;
      TopDrawingLayer.Parent = this;
      coChessBoard = new Hashtable();
      Square = rectangle;
      coFenNotation = new FenNotation();
      coFenNotation.AddEvents(PieceLayer);
    }

    #endregion Constructors

    /// <summary>
    /// This routines will ensure that our board locatoin and size is determined on if
    /// we are displaying a boarder around the board or not.
    /// </summary>
    void AdjustForBoarder()
    {
      // See if we have a boarder size set.
      if(BoarderLayer.BoarderWidth > 0 )
      {
        coBoardSquare = new Rectangle(Square.X + BoarderLayer.BoarderWidth, Square.Y + BoarderLayer.BoarderWidth,
          Square.Height - (BoarderLayer.BoarderWidth * 2),
          Square.Width  - (BoarderLayer.BoarderWidth * 2));
      }
      else
      {
        coBoardSquare = Square;
      }
      BoarderLayer.IsRedraw = true;
      BoardLayer.IsRedraw = true;
      PieceLayer.IsRedraw = true;
      TopDrawingLayer.IsRedraw = true;
      MidDrawingLayer.IsRedraw = true;
      // We have to rebuild our whole board.
      GenerateBoard();
      BoarderLayer.GenerateBoarders();
      if( Control != null )
        Control.Invalidate();
    }


    /// <summary>
    /// Used to generate our piece layout regarding square positions.
    /// This can change if the user request to flip the board position,
    /// i.e. see from whites or blacks perspective.
    /// </summary>
    void GenerateBoard()
    {     
      Rectangle square = new Rectangle();      
      square.Y = BoarderLayer.BoarderWidth;
      square.Width = coBoardSquare.Height / 8;
      square.Height = square.Width;

      PieceSquare ps;
      bool created = false;
      bool toggle = false;
      try
      {
        int ndxLayer=0;
        int ndxSquare = IsFlipped ? 0 : 63;
        string notation;
        // We're starting with the top right square so move over
        //  to the right side of the board.
        square.X = BoarderLayer.BoarderWidth + (square.Width * 7);
        // Populate all 64 squares.
        for(int ndx=63; ndx > -1; ndx--)
        {
          // Get our square notation.
          notation = BitboardSquare.SquareToAlpha(ndxSquare);
          // Check to see if it already exists in our array.
          if( coChessBoard.ContainsKey(notation ))
          {
            ps = (PieceSquare) coChessBoard[notation];
            created = false;
          }         
          else
          {
            // Must create our new square as it doesn't exist.
            ps = new PieceSquare();
            ps.Index = ndxLayer++;
            ps.SquareIndex = ndxSquare;
            ps.Name = notation;            
            coChessBoard.Add(notation,ps);
            created = true;
            ps.Color = toggle;

          }
          ps.Square = new Rectangle(square.X,square.Y,square.Width,square.Height);
          if( EventSquareGenerated != null )
          {
            EventSquareGenerated(ps,created);
          }         
          square.X -= square.Width;
          // Determine if we are display white or black on bottom of layer.
          if( IsFlipped ) 
            ndxSquare++; 
          else 
            ndxSquare--;
          
          toggle = !toggle;
         // See if we need to move to the next line of squares.
          if( ndx % 8 == 0 )
          {
            square.Y += square.Height;
            // We're starting with the right square so move over
            //  to the right side of the board.
            square.X = BoarderLayer.BoarderWidth + (square.Width * 7);
            toggle = !toggle;  
          }
        }
        // Fource a redraw of the board.
        IsRedraw = true;
      }
      catch(Exception ex)
      {
        String msg = ex.ToString();
        msg.Trim();
      }
    }
    /// <summary>
    /// Returns our PieceSquare object that contains information
    /// regarding the current algebraic reference square.  See
    /// the PieceSquare class for more information.
    /// </summary>
    /// <param name="square"></param>
    /// <returns></returns>
    public PieceSquare FindPiece(string square)
    {
      return (PieceSquare) coChessBoard[square];
    }
    public PieceSquare FindPiece(int square)
    {
      return FindPiece(BitboardSquare.SquareToAlpha(square));
    }

    public PieceSquare FindPiece(int x, int y)
    {
      PieceSquare found=null;
      foreach( PieceSquare ps in coChessBoard.Values)
      {        
        if(ps.Square.Contains(x,y))
        {
          found = ps;
          break;
        }
      }
      return found;
    }

    public delegate void SquareGenerated(PieceSquare ps, bool create);
    public event SquareGenerated EventSquareGenerated;


    #region IChessboardGui Members

    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public int BoarderWidth
    {
      get{ return BoarderLayer.BoarderWidth; }
      set
      { 
        BoarderLayer.BoarderWidth = value;
        AdjustForBoarder();
      }
    }
    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public Color DefaultDrawingColor
    {
      get
      {
        Color color = Color.Transparent;
        switch( coLayers )
        {
          case Layers.DrawMid:
          case Layers.DrawTop:
            color = ((DrawingLayer)coCurrentLayer).PenColor;
            break;
        }
        return color;
      }
      set
      {
        switch( coLayers )
        {
          case Layers.DrawMid:
          case Layers.DrawTop:
            ((DrawingLayer)coCurrentLayer).PenColor = value;
            break;
        }
      }
    }
    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public MoveGeneration MoveInput
    {
      set{ coMoveInput = value; }
      get{ return coMoveInput; }
    }

    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public Color AttacksColor
    {
      get{return coAttacksColor;}
      set{coAttacksColor = value;}
    }

    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public Color WhiteColor
    {
      get{return BoardLayer.WhiteColor;}      
    }
    public Color BoarderColor
    {
      get{ return BoarderLayer.BoarderColor; }
    }

    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public Color BlackColor
    {
      get{return BoardLayer.BlackColor;}      
    }

    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public int DefaultPenWidth
    {
      get
      {
        int width = 0;
        switch( coLayers )
        {
          case Layers.DrawMid:
          case Layers.DrawTop:
            width = ((DrawingLayer)coCurrentLayer).PenWidth;
            break;
        }
        return width;
      }
      set
      {
        switch( coLayers )
        {
          case Layers.DrawMid:
          case Layers.DrawTop:
            ((DrawingLayer)coCurrentLayer).PenWidth = value;
            break;
        }

      }
    }


    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public bool IsFlipped
    {
      get{return coIsFlipped;}
      set
      {
        coIsFlipped = value;
        BoarderLayer.GenerateBoarders();
        BoarderLayer.IsRedraw = true;
        BoardLayer.IsRedraw = true;
        PieceLayer.IsRedraw = true;
        GenerateBoard();
        if( Control != null )
          Control.Invalidate();
      }
    }
    public bool IsShowPieceDragging
    {
      get{ return PieceLayer.IsShowPieceDragging; }
      set{ PieceLayer.IsShowPieceDragging = value;}
    }

    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public bool IsLayerDragging
    {
      get
      { 
        bool dragging = false;
        foreach(IDrawable idraw in this)
        {
          if(idraw.IsDragging)
          {
            dragging = true;
            break;
          }
        }
        return dragging;
      }
    }

    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public string FenNotation 
    {
      get{ return PieceLayer.Validation != null ? PieceLayer.Validation.FenNotation : ""; }
      set
      {
        if(value.Length > 0 )
        {
          coFenNotation.Parse(value);
          IsRedraw = true;
          if(Control != null )
            Control.Invalidate();
        }
      }
    }

    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public string FenFormat
    {
      get{ return coFenNotation.Format; }
      set
      {
        coFenNotation.Format = value;
        if( PieceLayer.Validation != null )
          PieceLayer.Validation.FenFormat = value;
      }
    }
    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public string ImageFenNotation
    {
      get{ return PieceLayer.ToString();}
    }

    public ImageList ChessImages
    { 
      set{ PieceLayer.ChessImages = value;}
    }

    /// <summary>
    /// Here we provide a way to change the validtion engine that is used
    /// internally by our control.  Openning up the way for other variants
    /// to be created.
    /// </summary>
    public IValidation Validation
    {
      set
      {
        // First see if we had any previous validation assigned.
        if(PieceLayer.Validation != null)
        {
          // We must remove it from our FEN parser if it is available.
          coFenNotation.RemoveEvents(PieceLayer.Validation.PositionEvents);
        }
        // Set the new validation engine.
        PieceLayer.Validation = value;        
        if(value != null)
        {
          // Add it to our fen parser.
          coFenNotation.AddEvents(PieceLayer.Validation.PositionEvents);
        }
      }
      get
      {
        return PieceLayer.Validation; 
      }
    }
    /// <summary>
    /// This flag should be set to TRUE when we are editting a position.
    /// </summary>
    public bool IsEditting
    {
      get
      {
        return coIsEditting;
      }
      set
      {
        coIsEditting = value;
      }
    }

    public bool IsDrawing
    {
      get
      {
        return coIsDrawing;
      }
      set
      {
        coIsDrawing = value;
      }
    }
    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public void NewPosition()
    {
      FenNotation ="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
    }

    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public void ClearPosition()
    {
      FenNotation ="8/8/8/8/8/8/8/8 w ----";
    }

    /// <summary>
    /// Sets the active layer that may be manipulated by
    /// layer specific commands such as IsLayerEnabled.
    /// </summary>
    public Layers ActiveLayer
    {
      get
      {
        return coLayers;
      }
      set
      {
        coLayers = value;
        coCurrentLayer = null;
        switch(coLayers)
        {
          case Layers.Board:
            coCurrentLayer = BoardLayer;
            break;
          case Layers.Boarder:
            coCurrentLayer = BoarderLayer;
            break;
          case Layers.DrawMid:
            coCurrentLayer = MidDrawingLayer;
            break;
          case Layers.DrawTop:
            coCurrentLayer = TopDrawingLayer;
            break;
          case Layers.Piece:
            coCurrentLayer = PieceLayer;
            break;
          case Layers.Surface:
            coCurrentLayer = null;
            break;
        }
      }
    }

    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public bool IsLayerEnabled
    { 
      get
      { 
        bool enabled = false;
        if(coCurrentLayer != null )
          enabled = coCurrentLayer.IsEnabled;
        return enabled;
      } 
      set
      { 
        if(coCurrentLayer != null )
          coCurrentLayer.IsEnabled = value; 
      }
    }

    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public string LayerNotation
    {
      get
      {
        string str = null;
        if(coCurrentLayer != null )
          str = coCurrentLayer.ToString();
        return str;        
      }
      set
      {
        if(coCurrentLayer != null )
          coCurrentLayer.FromString(value);
      }
    }

    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public void Highlight(Color color, int width, string notation)
    {
      switch( coLayers )
      {
        case Layers.DrawMid:
        case Layers.DrawTop:
          ((DrawingLayer)coCurrentLayer).Highlight(color,width,notation);
          break;
      }
    }

    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public void Arrow(Color color, int width, string from, string to)
    {
      switch( coLayers )
      {
        case Layers.DrawMid:
        case Layers.DrawTop:
          ((DrawingLayer)coCurrentLayer).Arrow(color,width,from,to);
          break;
      }
    }

    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public void Circle(Color color, int width, string notation)
    {
      switch( coLayers )
      {
        case Layers.DrawMid:
        case Layers.DrawTop:
          ((DrawingLayer)coCurrentLayer).Circle(color,width,notation);
          break;
      }
    }

    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public void Xmarks(Color color, int width, string notation)
    {
      switch( coLayers )
      {
        case Layers.DrawMid:
        case Layers.DrawTop:
          ((DrawingLayer)coCurrentLayer).Xmarks(color,width,notation);
          break;
      }
    }

    public void Balloon(string text, int orientation, Rectangle rect)
    {
      switch( coLayers )
      {
        case Layers.DrawMid:
        case Layers.DrawTop:
          ((DrawingLayer)coCurrentLayer).Balloon(text,orientation,rect);
          break;
        case Layers.Surface:
          string name = "b:" + rect.X + ":" + rect.Y + ":" + rect.Width + ":" + rect.Height;
          if( orientation < 0 )
          {
            Remove(name);
          }
          else
          {
            // Always remove as we only want one with this name.
            Remove(name);
            DrawBalloon ds = new DrawBalloon(Control);
            ds.Settings = TopDrawingLayer.BalloonSettings;
            ds.AlphaBlending = 128;
            ds.Orientation = orientation;
            ds.Text = text;
            ds.Square = rect;
            ds.Name = name;
            Add(ds);
          }
          IsRedraw = true;
          break;
      }
    }

    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public void ClearLayer()
    {
      switch( coLayers )
      {
        case Layers.Surface:
          ClearUnlocked();
          break;
        default:
          if(coCurrentLayer != null )
          {
            coCurrentLayer.Clear();
          }
          break;
      }
    }

    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public void ColorBoarder(System.Drawing.Color color)
    {
      BoarderLayer.Color = color;
    }
    
    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public void ColorSquares(System.Drawing.Color white, System.Drawing.Color black)
    {
      BoardLayer.ColorSquares(white,black);
    }
    
    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public void ColorSquares(Bitmap colorWhite, Bitmap colorBlack)
    {
      BoardLayer.ColorSquares(colorWhite,colorBlack);
    }
    
    /// <summary>
    /// See IChessboardGui
    /// </summary>
    public void AlternateColorSquares(BitArray squareArray, System.Drawing.Color color)
    {
      BoardLayer.AlternateColorSquares(squareArray,null, color);
    }
    
    public void AlternateColorSquares(BitArray squareArray, int[] intensity, System.Drawing.Color color)
    {
      BoardLayer.AlternateColorSquares(squareArray, intensity, color);
    }

    public BitArray LastHighlights
    {
      get{ return BoardLayer.LastHighlights; }
    }

    public Bitmap ChessImage()
    {
      Render();
      return coBitmap.Clone() as Bitmap;
    }
    /// <summary>
    /// Locates a square by algebra notation and return a rectangle in relation to the
    /// chessboard.
    /// </summary>
    /// <param name="notation"></param>
    /// <returns></returns>
    public Rectangle NotationSquare(string notation)
    {
      PieceSquare ps = FindPiece(notation);
      if(ps != null)
        return ps.Square;
      return new Rectangle(0,0,0,0);
    }

    public void Hide(Pieces pieces, CompassHeading heading)
    {
      PieceLayer.Hide(pieces,heading);
      if( Control != null )
        Control.Invalidate();
    }

    #endregion
  }
  
}
